﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RentAWorker.DataAccess.Interfaces;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Diagnostics;
using RentAWorker.DataAccess.Entities;

namespace RentAWorker.DataAccess
{
    /// <summary>
    /// Implementation of the IResourceAcces.
    /// </summary>
    public class ResourceDataAccess : RentAWorker.DataAccess.Interfaces.IResourceAccess
    {
        /// <summary>
        /// Gets a single resource with location and rentalservices.
        /// </summary>
        /// <param name="resourceID">The resource ID.</param>
        /// <returns></returns>
        public RentResource GetResource(int resourceID)
        {
            using (BigDays2009Entities db = new BigDays2009Entities())
            {
                var query = (from r in db.RentResource.Include("RentalServices").Include("Location")
                             where r.ResourceID == resourceID
                             select r);

                var result = query.First();

                // Problem: When detaching an object all EntityReferences are lost
                // Solution:
                // Detach Location first
                Location l = result.Location;
                db.Detach(l);

                // Detach Resource
                db.Detach(result);

                // Connect detached location with resource
                result.Location = l;

                // Easier way to do it:
                // db.RentResource.MergeOption = MergeOption.NoTracking;
                // This will preserve the object tree.

                return result;
            }
        }

        /// <summary>
        /// Gets the resources.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<RentResource> GetResources()
        {
            using (BigDays2009Entities db = new BigDays2009Entities())
            {
                // Either detach or use NoTracking
                db.RentResource.MergeOption = MergeOption.NoTracking;
                return db.RentResource.Include("Location").ToList();
            }
        }


        /// <summary>
        /// Gets the resources.
        /// </summary>
        /// <param name="nameSearchPattern">The name search pattern.</param>
        /// <returns></returns>
        public IEnumerable<RentResource> GetResources(string nameSearchPattern)
        {
            using (BigDays2009Entities db = new BigDays2009Entities())
            {
                db.RentResource.MergeOption = MergeOption.NoTracking;

                return (from res in db.RentResource.Include("Location")
                        where (String.IsNullOrEmpty(nameSearchPattern) ||
                                res.Name.Contains(nameSearchPattern))
                        select res).ToList();
            }
        }

        /// <summary>
        /// Gets the available resources.
        /// </summary>
        /// <param name="nameSearchPattern">The name search pattern.</param>
        /// <param name="fromDate">From date.</param>
        /// <param name="toDate">To date.</param>
        /// <param name="locationId">The location id.</param>
        /// <param name="machineTypeId">The machine type id.</param>
        /// <returns></returns>
        public IEnumerable<RentResource>
            GetAvailableResources(string nameSearchPattern, DateTime fromDate, DateTime toDate, int locationId, int? machineTypeId)
        {
            using (BigDays2009Entities db = new BigDays2009Entities())
            {
                db.RentResource.MergeOption = MergeOption.NoTracking;

                // Create base query
                var query = from res in db.RentResource.Include("Location")
                            where (res.Location.LocationID == locationId) &&
                                ((from rentalService in res.RentalServices
                                  where (fromDate <= rentalService.StartDate && toDate >= rentalService.StartDate)
                                  || (fromDate >= rentalService.StartDate && fromDate <= rentalService.EndDate)
                                  select rentalService).Count() == 0)
                                && 
                                (String.IsNullOrEmpty(nameSearchPattern) 
                                    || res.Name.Contains(nameSearchPattern))
                            select res;

                IQueryable<RentResource> filteredQuery = null;

                // Extend the query by type of returned objects
                if (machineTypeId != null)
                {
                    filteredQuery = from machine in query.OfType<Machine>()
                                    where machine.MachineType.MachineTypeID == machineTypeId
                                    select (RentResource)machine;

                }
                else
                {
                    filteredQuery = from worker in query.OfType<Worker>()
                                    select (RentResource)worker;

                }
                // Execute the query
                return filteredQuery.ToList();
            }
        }

        /// <summary>
        /// Updates the resource.
        /// </summary>
        /// <param name="updateResource">The update resource.</param>
        /// <returns></returns>
        public RentResource UpdateResource(RentResource updateResource)
        {
            using (BigDays2009Entities db = new BigDays2009Entities())
            {
                RentResource originalResource = (RentResource)db.GetObjectByKey(updateResource.EntityKey);

                db.ApplyPropertyChanges(updateResource.EntityKey.EntitySetName, updateResource);
                db.ApplyReferencePropertyChanges(updateResource, originalResource);

                db.SaveChanges();

                return GetResource(updateResource.ResourceID);
            }
        }

        /// <summary>
        /// Creates the resource.
        /// </summary>
        /// <param name="newResource">The new resource.</param>
        /// <returns></returns>
        public RentResource CreateResource(RentResource newResource)
        {
            using (BigDays2009Entities db = new BigDays2009Entities())
            {
                // Variante 1:
                Location loc = newResource.Location;
                newResource.Location = null;
                newResource.LocationReference.EntityKey = loc.EntityKey;
                db.AddToRentResource(newResource);

                // Variante 2: (extra DB call)
                //Location loc = db.GetObjectByKey(newResource.Location.EntityKey) as Location;
                //newResource.Location = loc;

                // Variante 3:
                //Location loc = newResource.Location;
                //newResource.Location = null;
                //db.Attach(loc);
                //newResource.Location = loc;
                // NO AddToRentResource necessary!

                db.SaveChanges();

                return GetResource(newResource.ResourceID);
            }
        }

        /// <summary>
        /// Deletes the resource.
        /// </summary>
        /// <param name="resourceID">The resource ID.</param>
        public void DeleteResource(int resourceID)
        {
            using (BigDays2009Entities db = new BigDays2009Entities())
            {
                db.DeleteObject(db.RentResource.Where(r => r.ResourceID == resourceID).First());
                db.SaveChanges();
            }
        }
    }
}
